#region Copyright (c) 2003, newtelligence AG. All rights reserved.
/*
// Copyright (c) 2003, newtelligence AG. (http://www.newtelligence.com)
// Original BlogX Source Code: Copyright (c) 2003, Chris Anderson (http://simplegeek.com)
// All rights reserved.
//  
// Redistribution and use in source and binary forms, with or without modification, are permitted 
// provided that the following conditions are met: 
//  
// (1) Redistributions of source code must retain the above copyright notice, this list of 
// conditions and the following disclaimer. 
// (2) Redistributions in binary form must reproduce the above copyright notice, this list of 
// conditions and the following disclaimer in the documentation and/or other materials 
// provided with the distribution. 
// (3) Neither the name of the newtelligence AG nor the names of its contributors may be used 
// to endorse or promote products derived from this software without specific prior 
// written permission.
//      
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -------------------------------------------------------------------------
//
// Original BlogX source code (c) 2003 by Chris Anderson (http://simplegeek.com)
// 
// newtelligence is a registered trademark of newtelligence Aktiengesellschaft.
// 
// For portions of this software, the some additional copyright notices may apply 
// which can either be found in the license.txt file included in the source distribution
// or following this notice. 
//
*/
#endregion

using System;
using System.Collections;
using System.Xml.Serialization;

namespace newtelligence.DasBlog.Runtime
{
    /// <summary>
    /// 
    /// </summary>
    [XmlRoot(Namespace=Data.NamespaceURI)]
    [XmlType(Namespace=Data.NamespaceURI)]
    public class EntryIdCache
    {
        private static long _changeNumber;
        private static bool _booting = true;
        private static EntryIdCacheEntryCollection _entries = null;
		private static object _entriesLock = new object();
		private static Hashtable _entryIDToDate = new Hashtable(CaseInsensitiveHashCodeProvider.DefaultInvariant,CaseInsensitiveComparer.DefaultInvariant);
		private static Hashtable _compressedTitleToDate = new Hashtable(CaseInsensitiveHashCodeProvider.DefaultInvariant,CaseInsensitiveComparer.DefaultInvariant);

        [XmlIgnore]
        public string FileName { get { return "entryCache.xml"; } }

        public long ChangeNumber { get { return _changeNumber; } set { _changeNumber = value; } }
        public EntryIdCacheEntryCollection Entries { get { return _entries; } set { _entries = value; } }
		[XmlIgnore]
		public Hashtable EntryIdToDate { get { return _entryIDToDate; }  }
		[XmlIgnore]
		public Hashtable CompressedTitleToDate { get { return _entryIDToDate; }  }

        internal void Ensure(DataManager data)
        {
			lock(_entriesLock)
			{
				bool bLoaded = Load(data);
				if (!bLoaded || _booting || ChangeNumber != data.CurrentEntryChangeCount)
				{
					_booting = false;
					Build(data);
					Save(data);
				}
            }
        }

        internal void Build(DataManager data)
        {
			EntryIdCacheEntryCollection entriesCopy = new EntryIdCacheEntryCollection();
            Hashtable entryIdToDateCopy = new Hashtable(CaseInsensitiveHashCodeProvider.DefaultInvariant,CaseInsensitiveComparer.DefaultInvariant);
			Hashtable compressedTitleToDateCopy = new Hashtable(CaseInsensitiveHashCodeProvider.DefaultInvariant,CaseInsensitiveComparer.DefaultInvariant);
		
			try
			{
				foreach (DayEntry day in data.Days)
				{
					day.Load(data);

					foreach (Entry entry in day.Entries)
					{
						EntryIdCacheEntry ec = new EntryIdCacheEntry();
						ec.DateUtc = entry.CreatedUtc;
						ec.EntryId = entry.EntryId;
						ec.Title = entry.Title;
						ec.IsPublic = entry.IsPublic;
						entriesCopy.Add(ec);
		
						entryIdToDateCopy.Add(entry.EntryId,ec.DateUtc.Date);
						
						//SDH: Only the first title is kept, in case of duplicates
						// TODO: should be able to fix this, but it's a bunch of work.
						string compressedTitle = ec.CompressedTitle;
						compressedTitle = compressedTitle.Replace("+", "");
						if (compressedTitleToDateCopy.ContainsKey(compressedTitle) == false)
						{
							compressedTitleToDateCopy.Add(compressedTitle,ec.DateUtc.Date);
						}
					}
				}
			}
			//TODO: SDH: Temporary, as sometimes we get collection was modified from the EntryCollection...why does this happen? "Database" corruption? Misaligned Entries?
			catch(InvalidOperationException) //something wrong enumerating the entries?
			{
				//set flags to start over to prevent getting stuck...
				_booting = true; 
				ChangeNumber = 0;
				throw;
			}
			//try to be a little more "Atomic" as others have been enumerating this list...
			_entryIDToDate.Clear();
			_entryIDToDate = entryIdToDateCopy;
			_compressedTitleToDate.Clear();
			_compressedTitleToDate = compressedTitleToDateCopy;
            _changeNumber = data.CurrentEntryChangeCount;
            _entries = entriesCopy;
        }

		internal string GetTitleFromEntryId(string entryid)
		{
			object retVal = _entries[entryid];
			if (retVal == null)
				return null;
			string title = ((EntryIdCacheEntry)retVal).Title;
			return title;
		}

		internal DateTime GetDateFromEntryId(string entryid)
		{
			object retVal = _entryIDToDate[entryid];
			if (retVal == null)
				return DateTime.MinValue;
			return (DateTime)retVal;
		}

		internal DateTime GetDateFromCompressedTitle(string title)
		{
			object retVal = _compressedTitleToDate[title];
			if (retVal == null)
				return DateTime.MinValue;
			return (DateTime)retVal;
		}

		private bool Load(DataManager data)
		{
			//return true if we are loaded
			if (_entries == null)
			{
				_entries = EntryIdCacheEntryCollection.Synchronized(new EntryIdCacheEntryCollection());
				return false;
			}
			return true;

			//EntryIdCache e = HttpContext.Current.Cache["EntryIdCache"] as EntryIdCache;
			/*
			 * EntryIdCache e = HttpRuntime.Cache["EntryIdCache"] as EntryIdCache;
			if (e != null)
			{
				_entryIDToDate = e.EntryIdToDate;
				_compressedTitleToDate = e.CompressedTitleToDate;
				_entries = e.Entries;
				_changeNumber = e.ChangeNumber;
				return true;
			}
			return false;
			*/

			//SDH: Other than preventing a rebuild on a recycle, there's just NO REASON
			//     to store the cache on disk. We save million of read/writes on my site
			//     with this small change.

			/*
			string fullPath = data.ResolvePath(FileName);
			if (File.Exists(fullPath))
			{
				FileStream fileStream = FileUtils.OpenForRead(fullPath);
				if ( fileStream != null )
				{
					try
					{
						XmlSerializer ser = new XmlSerializer(typeof(EntryIdCache),Data.NamespaceURI);
                
						using (StreamReader reader = new StreamReader(fileStream))
						{
							XmlNamespaceUpgradeReader upg = new XmlNamespaceUpgradeReader( reader, "", Data.NamespaceURI );
                            EntryIdCache e = (EntryIdCache)ser.Deserialize(upg);
                            _entries = e.Entries;
                            _changeNumber = e.ChangeNumber;
						}
					}
					catch(Exception e)
					{
						ErrorTrace.Trace(System.Diagnostics.TraceLevel.Error,e);
					}
					finally
					{
						fileStream.Close();
					}
				}
			}
			*/
		}

        private void Save(DataManager data)
        {
			//HttpContext.Current.Application["EntryIdCache"] = this;
			//HttpRuntime.Cache.Insert("EntryIdCache",this,null,DateTime.MaxValue,TimeSpan.Zero,System.Web.Caching.CacheItemPriority.NotRemovable,null);

			//SDH: Other than preventing a rebuild on a recycle, there's just NO REASON
			//     to store the cache on disk. We save million of read/writes on my site
			//     with this small change.

			/*
			string fullPath = data.ResolvePath(FileName);

            FileStream fileStream = FileUtils.OpenForWrite(fullPath);
            if ( fileStream != null )
            {
                try
                {
                    XmlSerializer ser = new XmlSerializer(typeof(EntryIdCache),Data.NamespaceURI);
                    using (StreamWriter writer = new StreamWriter(fileStream))
                    {
                        ser.Serialize(writer, this);
                    }
                }
                catch(Exception e)
                {
                    ErrorTrace.Trace(System.Diagnostics.TraceLevel.Error,e);
                }
                finally
                {
                    fileStream.Close();
                }
            }
			*/
        }
    }
}
